13.2.2 容器与持久化数据
在容器中持久化数据的方式推荐采用卷。
总体来说,用户创建卷,然后创建容器,接着将卷挂载到容器上。卷会挂载到容器文件系统的某个目录之下,任何写到该目录下的内容都会写到卷中。即使容器被删除,卷与其上面的数据仍然存在。
如图13.1所示,Docker卷挂载到容器的 /code
目录。任何写入 /code
目录的数据都会保存到卷当中,并且在容器删除后依然存在。
图13.1中, /code
目录是一个Docker卷。容器其他目录均使用临时的本地存储。卷与目录 /code
之间采用带箭头的虚线连接,这是为了表明卷与容器是非耦合的关系。
1.创建和管理容器卷
Docker中卷属于一等公民。抛开其他原因,这意味着卷在API中拥有一席之地,并且有独立的 docker volume
子命令。
使用下面的命令创建名为 myvol
的新卷。
$ docker volume create myvol
默认情况下,Docker创建新卷时采用内置的 local
驱动。恰如其名,本地卷只能被所在节点的容器使用。使用 -d
参数可以指定不同的驱动。
第三方驱动可以通过插件方式接入。这些驱动提供了高级存储特性,并为Docker集成了外部存储系统。图13.2展示的就是外部存储系统被用作卷存储。驱动集成了外部存储系统到Docker环境当中,同时能使用其高级特性。
截至本书撰写时,已经存在25种卷插件,涵盖了块存储、文件存储、对象存储等。
- 块存储: 相对性能更高,适用于对小块数据的随机访问负载。目前支持Docker卷插件的块存储例子包括HPE 3PAR、Amazon EBS以及OpenStack块存储服务(Cinder)。
- 文件存储: 包括NFS和SMB协议的系统,同样在高性能场景下表现优异。支持Docker卷插件的文件存储系统包括NetApp FAS、Azure文件存储以及Amazon EFS。
- 对象存储: 适用于较大且长期存储的、很少变更的二进制数据存储。通常对象存储是根据内容寻址,并且性能较低。支持Docker卷驱动的例子包括Amazon S3、Ceph以及Minio。
现在卷已经创建成功,读者可以通过 docker volume ls
命令进行查看,还可以使用 docker volume inspect
命令查看详情。
$ docker volume ls
DRIVER VOLUME NAME
local myvol
$ docker volume inspect myvol
[
{
"CreatedAt": "2018-01-12T12:12:10Z",
"Driver": "local",
"Labels": {},
"Mountpoint": "/var/lib/docker/volumes/myvol/_data",
"Name": "myvol",
"Options": {},
"Scope": "local"
}
]
inspect
命令输出中有几点很有意思。Driver和Scope都是 local
。这意味着卷使用默认 local
驱动创建,只能用于当前Docker主机上的容器。 Mountpoint
属性说明卷位于Docker主机上的位置。在本例中卷位于Docker主机的 /var/lib/docker/volumes/myvol/_data
目录。在Windows Docker主机上对应内容为 Mountpoint": "C:\\ProgramData\\Docker\\ volumes\\myvol\\_data
。
使用 local
驱动创建的卷在Docker主机上均有其专属目录,在Linux中位于 /var/lib/docker/volumes
目录下,在Windows中位于 C:\ProgramData\Docker\volumes
目录下。这意味着可以在Docker主机文件系统中查看卷,甚至在Docker主机中对其进行读取数据或者写入数据操作。在第9章中就有一个示例——复制某个文件到Docker主机的卷目录下,在容器该卷中立刻就能看到对应的文件。
读者可以在Docker服务以及容器中使用 myvol
卷了。例如,可以在 docker container run
命令后增加参数 --flag
将卷挂载到新建容器中。稍后通过几个例子进行说明。
有两种方式删除Docker卷。
docker volume prune
。docker volume rm
。
docker volume prune
会删除未装入到某个容器或者服务的 所有卷 ,所以 谨慎使用 ! docker volume rm
允许删除指定卷。两种删除命令都不能删除正在被容器或者服务使用的卷。
因为没有使用 myvol
卷,所以请通过 prune
命令进行删除。
$ docker volume prune
WARNING! This will remove all volumes not used by at least one container.
Are you sure you want to continue? [y/N] y
Deleted Volumes:
myvol
Total reclaimed space: 0B
恭喜,现在已经完成创建、查看以及删除卷的操作了。上述操作均未涉及容器,这也验证了卷是独立的这一特性。
到目前,读者已经了解了卷的创建、列表、查看以及删除命令。此外,还可以通过在Dockerfile中使用 VOLUME
指令的方式部署卷。具体的格式为 VOLUME <container-mount-point
。但是,在Dockerfile中无法指定主机目录。这是因为主机目录通常情况下是相对主机的一个目录,意味着这个目录在不同主机间会变化,并且可能导致构建失败。如果通过Dockerfile指定,那么每次部署时都需要指定主机目录。
2.演示卷在容器和服务中的使用
现在读者已经了解了卷相关的基本命令,接下来看一下如何在容器和服务中使用卷。
接下来的内容是基于某个没有卷的系统,演示内容适用于Linux和Windows。
使用下面的命令创建一个新的独立容器,并挂载一个名为 bizvol
的卷。
Linux示例如下。
$ docker container run -dit --name voltainer \
--mount source=bizvol,target=/vol \
alpine
Windows示例如下。
所有的Windows示例都在PowerShell中执行,请注意反引号(`)用于将命令拆至多行。
> docker container run -dit --name voltainer `
--mount source=bizvol,target=c:\vol `
microsoft/powershell:nanoserver
即使系统中没有叫作 bizvol
的卷,命令也应该能够成功运行。这里引出了很有意思的一点。
- 如果指定了已经存在的卷,Docker会使用该卷。
- 如果指定的卷不存在,Docker会创建一个卷。
在当前示例中, bizvol
这个卷并不存在,所以Docker新建一个卷并挂载到新容器内部。这意味着读者可以通过 docker volume ls
命令看到该卷。
$ docker volume ls
DRIVER VOLUME NAME
local bizvol
尽管容器和卷各自拥有独立的生命周期,Docker也不允许删除正在被容器使用的卷。
$ docker volume rm bizvol
Error response from daemon: unable to remove volume: volume is in use -
[b44 d3f82...dd2029ca]
目前卷是空的。执行 exec
连接到容器并向卷中写入一部分数据。示例引用的是Linux,如果读者使用Windows示例,则需要将 docker container exec
命令结尾的 sh
替换为 pwsh.exe
。其他命令在Linux和Windows上面均可以生效。
$ docker container exec -it voltainer sh
/# echo "I promise to write a review of the book on Amazon" > /vol/file1
/# ls -l /vol
total 4
-rw-r--r-- 1 root root 50 Jan 12 13:49 file1
/# cat /vol/file1
I promise to write a review of the book on Amazon
输入 exit
命令返回到Docker主机Shell中,然后使用下面命令删除容器。
$ docker container rm voltainer -f
voltainer
即使容器被删除,卷依旧存在。
$ docker container ls -a
CONTAINER ID IMAGE COMMAND CREATED STATUS
$ docker volume ls
DRIVER VOLUME NAME
local bizvol
由于卷仍然存在,因此可以进入到其在主机的挂载点并查看前面写入的数据是否还在。
在Docker主机的终端上执行下面的命令。第一条命令会证明文件依然存在,第二条命令展示了文件的内容。
如果是Windows示例,一定要使用 C:\ProgramData\Docker\volumes\bizvol\_data
目录。
$ ls -l /var/lib/docker/volumes/bizvol/_data/
total 4
-rw-r--r-- 1 root root 50 Jan 12 14:25 file1
$ cat /var/lib/docker/volumes/bizvol/_data/file1
I promise to write a review of the book on Amazon
太棒了,卷和数据都还在。
甚至将 bizvol
挂载到一个新的服务或者容器都是可以的。下面的命令会创建一个名为 hellcat
的新Docker服务,并且将 bizvol
挂载到该服务副本的 /vol
目录。
$ docker service create \
--name hellcat \
--mount source=bizvol,target=/vol \
alpine sleep 1d
overall progress: 1 out of 1 tasks
1/1: running [====================================>]
verify: Service converged
上述命令没有指定--replicas参数,所以服务只会部署一个副本。找到Swarm集群中运行了该服务的节点。
$ docker service ps hellcat
ID NAME NODE DESIRED STATE CURRENT STATE
l3nh... hellcat.1 node1 Running Running 19 seconds ago
在本例中,副本运行在node1节点上。登录到node1节点,然后获取服务副本容器ID。
node1$ docker container ls
CTR ID IMAGE COMMAND STATUS NAMES
df6..a7b alpine:latest "sleep 1d" Up 25 secs hellcat.1.l3nh...
注意,容器的名称包括了 service-name
、 replica-number
以及 replica-ID
,采用句号分隔。
登录到该容器并检查数据是否在 /vol
中。在exec例子中会使用服务副本的容器ID。如果读者使用Windows示例,记得将 sh
替换为 pwsh.exe
。
node1$ docker container exec -it df6 sh
/# cat /vol/file1
I promise to write a review of the book on Amazon
这样卷中保存了原始数据,并且在新容器中也可以使用。